"Show Movie" is a small application designed to load and play movies. It demonstrates several useful features in QuickTime and ways to use them:
-Creating a Time Base call back
-Setting call backs based on the length of a movie.
-Automatically closing a movie once it has finished playing
-Making a loop within a movie.
-Changing the rate of a movie while it is playing.
-Slaving one movie to another.
-Offsetting the start frame of a slave movie.
-Delaying the start of a slave movie playing
It achieves the above by using Time Base call backs documented in Inside Macintosh QuickTime pages 2-335 to 2-341, 2-364
It also demonstrates
Ways to pass data to the call backs and dispose of it once the movie has finished.
How to create a default button on a dialog without using user items.
It's not intended to be a definitive 'document' on how to implement these features, but illustrates one way of doing it.
How to use the Application
There are two ways to open a window. "Open Movie…" opens an options dialog box, uses standard file to select a movie, then opens a movie in a window. "Open Master & Slave…" opens the options dialog (which is the same as above with a couple of extra items enabled), then asks for two movies and opens them in two windows titled "Master" and "Slave". They can be the same movie; they don't have to be, though it helps if they are roughly the same length. You can close the movie by selecting the go away box or "close" menu item. If you close a "Master" window, it will also close it's "slave". New movies can be opened while others are still playing.
Options Dialog
"Close at end"
When set, the window will close when it's movie has finished playing. If it is a "Master" window then it's slave will close as well.
"Has movie controller"
Selecting it will display the standard movie controller underneath the movie. If you're opening a master and slave, only the master will receive a controller. If the movie doesn't have a controller, then it will autostart when the movie is opened.
"20 Seconds in, Loop back to 10"
After the movie has played for 20 seconds, it jumps back to 10 seconds from the start.
"Change Movie Rate"
This is a pop up menu which has three items. "Don't Change" means nothing will happen. "on Thirds" plays the movie at normal speed for the first 1/3 of the movie, double speed for the middle third and half speed for the final third. Note that this 1/3 refer to the original duration. For example if the movie is 60 seconds long, the first third will play for 20 seconds, the second third (at double speed) will take 10 seconds and the final third (at half speed) will take 40 seconds giving a total play time of 70 seconds! The final item "on 10 Seconds" plays the first 10 seconds at normal speed, the next 10 seconds at double speed and the remainder of the movie at half speed. Not that the second 10 second block will take only 5 seconds to play as the timings refer to the original durations of the movie.
The next two items are only available for master & slave movies
"Slave Ahead by"
This is a pop up that decides what frame the slave will start on. It has three options "in Sync","One Third" and "10 seconds". "in Sync" will make the slave start at the beginning. If set to "One Third", the slave will start at 1/3 of it's duration. "10 seconds" and the slave start frame will 10 seconds from it's start.
"Delay Slave Start"
This pop up delays when the slave actually starts playing. Again it has three options. "No Delay" will make the slave start playing at the same time as the master. "One Third" will make the slave start one third of the way through the master movie. "10 Seconds" will delay the start of the movie for 10 seconds.
Using combinations of the above two can generate interesting effects. Using "Delay Slave Start" on it's own will run the slave movie behind the Master which is the opposite of "Slave ahead by". If both are set the same, then the slave will delay it's start, then run in sync with the master.
"Help"
This changes when you release the mouse and displays a (very) brief explanation about what setting you have just changed and what it's new state does.
Building ShowMovie
Show Movie compiles under :
Metrowerks CodeWarrior 7
Symantec C++ 8.0.1
Symantec 7.0.4
MPW E.T.O. #18- 'Latest MPW': MPW C, PPCC, Symantec C++ for MPW and MrC.
The Symantec environments are using a slightly older version of the Universal Interfaces than MPW and CodeWarrior. You will need to use a later version of the Universal Interfaces than is provided with the Symantec products, or make some changes to the source code. To change the Universal Interfaces, simply place brackets around the existing folder and place the folder containing the later version into the same folder as the existing ones. The brackets will prevent the development environment from using the files contained within the old folder.
An MPW make file to build a 'fat' binary of ShowMovie using 'Latest MPW' is included. This defaults to using MPW C and PPCC. You can assign the {C} and {PPCC} variables to SC and MrC at the top of the make file if you have these compilers installed. You will need to alter the compiler options as appropriate and use the 'Latest MPW' interfaces and libraries rather than SCLibraries and SCIncludes. To build with 'PreRelease MPW' create a make file using 'Create Build Commands' and ensure that 'qd' is defined in all cases, since the PreRelease MPW Libraries do not define 'qd'.
Files
Show Movie.c
- contains "main" function
- handles the event loop
WindStuff.c
- creates and disposes of windows
- general window handling
- creates and handles the About Box
MenuStuff.c
- creates the menu bar
- handles clicks within the menu bar
- calls code to create windows and setup movies depending on the options
MovieStuff.c
- creates and disposes of information relating to movies
- handles events relating to movies
- contains code to set up any options that have been requested
This is a linked list of all the data used by a callback. At the end of the record it includes a variable sized block of data used by the call back itself. The CallBackInfoHdl can be passed in the "refCon" parameter of "CallMeWhen" and it is passed into the call back in the "refCon" parameter.
CallBackInfoType also has to contain all the information used by "CallMeWhen" as once a call back has been invoked, "CancelCallBack" is called by the tool box. This removes the it from the time base. Therefore, if we want this call back to happen again (e.g. we're creating a loop), then we need to reschedule it, so the call back can be invoked again. This is done by calling "CallMeWhen" with the stored parameters.
How the interesting stuff works
Creating a Time Base call back
Most of the interesting stuff in Show Movie is handled by time based call backs. These call backs can be invoked at predetermined times, when the movie's rate of playback reaches a specified value, when the movie jumps, or at the start or end of the movie. In general the call backs used in show movie, are triggered at a specified time.
First you need to get the movies time base. Use "GetMovieTimeBase" to extract this from the movie. You will also probably need the movies time scale, so also call "GetMovieTimeScale". In Show Movie this is done when the movie is created.
"callBackAtTime" is a constant which causes it to be invoked at a specified time. If it fails, it will return nil, so check for that. Next I need to calculate when I want to invoke my call back. By multiplying the time (in seconds) by the movies time scale we'll get a value we can use.
Finally to create the call back, use "CallMeWhen"
theErr = CallMeWhen( myCallBack,
myProc, // UPP (as we're PowerPC friendly)
callBackRefCon, // Passed to the call back
triggerTimeFwd, // or triggerTimeBwd or triggerTimeEither (p2.337)
timeToDoIt * moviesTimeScale, // Time to trigger it
moviesTimeScale );
We have to store myCallBack and dispose of it using "DisposeCallBack" when we close the window. Information to the call back can be past to it in the callBackRefCon. If we want to store something slightly larger, we can pass it as handle or pointer in the refCon. This is where Show Movie comes to the rescue. After NewCallBack, I create and store the data using "AddNewCallBackInfo" which adds a CallBackInfoHdl into the chain hanging of DocMovieInfoHndl(i.e. the windows refCon) and returns it. When I use CallMeWhen, I pass it as the refCon. So the whole block becomes :
The call back needs to be in the following format:
pascal void MyCallBack (QTCallBack callBack, long refcon);
"callBack" refers to the structure created by "NewCallBack". The "refCon" contains the value passed in the 2nd parameter of "CallMeWhen". In the case of "Show Movie" this is a CallBackInfoHdl. At the end of CallBackInfoHdl is a block of data (someData) that we added for this call back to use. Show Movie has a function to extract this called "GetDataFromCBInfo".
One other thing to bear in mind, is once the call back has been invoked, the Movie Toolbox calls "CancelCallBack", so if you want it to happen again, (say in a loop), we have to reschedule the event using "CallMeWhen" . So our call back now looks like.
pascal void MyCallBack (QTCallBack callBack, long refcon)
{
SomeDataType someData;
OSErr theErr = noErr;
if (GetDataFromCBInfo ( (CallBackInfoHdl)refcon,
(Ptr) &someData,
sizeof ( SomeDataType ) ))
{
// Now do something with it !!!!
}
// Finally must reschedule the call back so that it can happen again (Optional)
theErr = CallMeWhen ( callBack,
MyCallBackUpp, refcon,
(**(CallBackInfoHdl)refcon).param1,
(**(CallBackInfoHdl)refcon).param2,
(**(CallBackInfoHdl)refcon).param3);
}
When the window is closed Show Movie disposes of these structures
RemoveCallBackInfo ( hFirstItem );
Setting call backs based on the length of a movie.
Normally, you calculate the position of a call back by multiplying the time in seconds by the movies time scale. To set a call back based on the length of a movie, use GetMovieDuration to return the length of the movie and divide or multiply by the appropriate amount.
E.g. to do something half way through
long halfWayThrough;
TimeValue theMovieLen;
theMovieLen = GetMovieDuration( theMovie );
halfWayThrough = theMovieLen*0.5;
You can use halfWayThrough as the trigger time for the call back
Automatically closing a movie once it has finished playing
This all done in a routine called "ServiceMovieTasks" in MovieStuff.c. This should be called after WaitNextEvent and it does three things. It scans the window list and checks if the event needs to be passed to a window's movie controller. Next it sees if the window's movie is running using "IsMovieDone". If it has finished and should be closed once finished, it closes the window. Finally, if there is still a movie playing, it calls "MoviesTask" which services all the movies.
// Finally must reschedule the call back so that it happens again
theErr = CallMeWhen ( myCallBack,
gAlterMasterOffsetUpp, ref,
(**(CallBackInfoHdl)ref).param1,
(**(CallBackInfoHdl)ref).param2,
(**(CallBackInfoHdl)ref).param3);
}
}
Changing the rate of a movie while it is playing.
This is done in "SetupMovieRate" and the call back "AlterRate".
The call back is setup as above, but this time the data is the new rate we want the movie to run at. The call back "AlterRate" extracts the new rate and sets it using "SetMovieRate".
The next bit calculates the offset between the two movies. We use SetTimeBaseValue to do this and as the slave uses the master's time base, what we do is set up an offset between the two movies.
Finally we need to put a call back into the master movie to make sure the slave starts when the master starts. We can also use this to add a delay to the start of the slave movie.
We calculate when we want to start the movie, then add a call back that starts the movie at the appropriate time. We put this call back into the masters time base.
Default Buttons in dialogs
Most applications put a default box round the OK button by creating a user item and drawing a round rect. System 7 included some new calls to the dialog manager that were not discussed in Inside Mac but are buried in a Technote ( Toolbox - TB 37 - Pending Update Perils). "SetDialogDefaultItem" sets the Ok button, "SetDialogCancelItem" sets the cancel item, "SetDialogTracksCursor" converts the cursor to an I Beam when you mouse over a text edit field. The gotcha is that you must call the StdFilterProc. You can get this with "GetStdFilterProc". If you have a custom filter proc, it must call this as well. I strongly suggest looking at Technote TB 37 for a fuller explanation.
Anyway armed with this knowledge, my about box looks like:
void AboutBox(void)
{
GrafPtr savePort = nil;
DialogPtr aboutDialog;
ModalFilterUPP theFilter = nil;
short itemHit = 0; // dialog item we've clicked on
DebugStr("\pFailed to get standard dialog filter.");
// Set item 1 - <OK> to have a default box around it
SetDialogDefaultItem(aboutDialog,1);
// Modal dialog loop
do {
// Use "theFilter" in ModalDialog call
ModalDialog(theFilter,&itemHit);
} while (itemHit != 1);
DisposeDialog(aboutDialog);
SetPort(savePort);
}
Because I'm using the standard filter, I don't have to worry about checking for the a [Return] key press or drawing a box around the <OK> button.
Further things YOU could do
If you feel like playing with this app, there's a few ways you could improve it.
- Make the movie loop from the end back to the start.
Use "callBackAtExtremes" in NewCallBack and "triggerAtStop" in CallMeWhen
- Allow the user to enter his own values for when it loops.
- More flexibility about when the rate changes and by how much.
- Allow user to enter the offset and delay values for slave.
All these needs are a nice user interface. You could also change them so they handle fractions of a second.
- Dynamically change options
You could do this by using "NewCallBack" to create all the call backs you might need but only insert the ones that you initially need (using "CallMeWhen"). "CancelCallBack" and "CallMeWhen" could be used to extract and insert call backs when you wanted them, though you'll need some modifications to the data structure to identify which call back does what.